package models

import (
	"fmt"
	"log"
	"strings"

	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/postgres"

	"gin-demo/pkg/setting"
	"time"
)

type Version string

const (
	VersionPre  = "pre"
	VersionV2p6 = "v2.6"
)

var db *gorm.DB
var ErrRecordNotFound = gorm.ErrRecordNotFound

// DB *gorm.DB
func DB() *gorm.DB {
	return db
}

type Model struct {
	ID         int `gorm:"primary_key; AUTO_INCREMENT" json:"id"`
	AddTime    int `json:"add_time"`
	UpdateTime int `json:"update_time"`
}

// Setup initializes the database instance
func SetupDB() {
	var err error
	db, err = gorm.Open(setting.DatabaseSetting.Type, fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
		setting.DatabaseSetting.Host,
		setting.DatabaseSetting.Port,
		setting.DatabaseSetting.User,
		"aiyunji1605",
		setting.DatabaseSetting.Name))

	if err != nil {
		log.Fatalf("models.Setup err: %v", err)
	}

	gorm.DefaultTableNameHandler = func(db *gorm.DB, defaultTableName string) string {
		if strings.HasPrefix(defaultTableName, setting.DatabaseSetting.TablePrefix) {
			return defaultTableName
		}
		return setting.DatabaseSetting.TablePrefix + defaultTableName
	}
	// 日志模式 设置为true之后控制台会输出对应的SQL语句
	if setting.DatabaseSetting.LogMode {
		db.LogMode(setting.DatabaseSetting.LogMode)
	}

	db.SingularTable(true)
	db.Callback().Create().Replace("gorm:update_time_stamp", updateTimeStampForCreateCallback)
	db.Callback().Update().Replace("gorm:update_time_stamp", updateTimeStampForUpdateCallback)
	//db.Callback().Delete().Replace("gorm:delete", deleteCallback)
	db.DB().SetMaxIdleConns(10)
	db.DB().SetMaxOpenConns(100)
	db.DB().SetConnMaxLifetime(30 * time.Minute)
	db.AutoMigrate(&Model{})
	initAdmin()
}

// CloseDB closes database connection (unnecessary)
func CloseDB() {
	defer db.Close()
}

// updateTimeStampForCreateCallback will set `CreatedOn`, `ModifiedOn` when creating
func updateTimeStampForCreateCallback(scope *gorm.Scope) {
	if !scope.HasError() {
		nowTime := time.Now().Unix()
		if createTimeField, ok := scope.FieldByName("AddTime"); ok {
			if createTimeField.IsBlank {
				createTimeField.Set(nowTime)
			}
		}

		if modifyTimeField, ok := scope.FieldByName("UpdateTime"); ok {
			if modifyTimeField.IsBlank {
				modifyTimeField.Set(nowTime)
			}
		}
	}
}

// updateTimeStampForUpdateCallback will set `ModifiedOn` when updating
func updateTimeStampForUpdateCallback(scope *gorm.Scope) {
	if _, ok := scope.Get("gorm:update_column"); !ok {
		scope.SetColumn("UpdateTime", time.Now().Unix())
	}
}

// deleteCallback will set `DeletedOn` where deleting
func deleteCallback(scope *gorm.Scope) {
	if !scope.HasError() {
		var extraOption string
		if str, ok := scope.Get("gorm:delete_option"); ok {
			extraOption = fmt.Sprint(str)
		}

		deletedOnField, hasDeletedOnField := scope.FieldByName("DeletedOn")

		if !scope.Search.Unscoped && hasDeletedOnField {
			scope.Raw(fmt.Sprintf(
				"UPDATE %v SET %v=%v%v%v",
				scope.QuotedTableName(),
				scope.Quote(deletedOnField.DBName),
				scope.AddToVars(time.Now().Unix()),
				addExtraSpaceIfExist(scope.CombinedConditionSql()),
				addExtraSpaceIfExist(extraOption),
			)).Exec()
		} else {
			scope.Raw(fmt.Sprintf(
				"DELETE FROM %v%v%v",
				scope.QuotedTableName(),
				addExtraSpaceIfExist(scope.CombinedConditionSql()),
				addExtraSpaceIfExist(extraOption),
			)).Exec()
		}
	}
}

// addExtraSpaceIfExist adds a separator
func addExtraSpaceIfExist(str string) string {
	if str != "" {
		return " " + str
	}
	return ""
}

// generate super admin
func initAdmin() {

	//tx := db.Debug().Begin()
	//defer func() {
	//	if r := recover(); r != nil {
	//		_ = tx.Rollback().Error
	//	}
	//}()
	//noncestr := util.GetRandomString(setting.CharStr, 16)
	//// aiyunji1605 ==> b61f5abf2cdb82b241cf1a875c30815c
	//password := util.EncodeMD5(util.EncodeMD5("aiyunji1605", ""), noncestr)
	//admin := Admins{Username: "lpmadmin", NonceStr: noncestr, Password: password, Name: "超级管理员", Identify: 1,
	//	Status: 1, Phone: "15820270510"}
	//outAdmin := Admins{}
	//err := tx.Where(&Admins{Username: "lpmadmin"}).First(&outAdmin).Error
	//if err != nil {
	//	if gorm.IsRecordNotFoundError(err) {
	//		if err := tx.Create(&admin).Error; err != nil {
	//			tx.Rollback()
	//			return
	//		}
	//		tx.Where(&Admins{Username: "lpmadmin"}).First(&outAdmin)
	//	} else {
	//		tx.Rollback()
	//		return
	//	}
	//}
	//superRole := Role{Identifier: "superAdmin"}
	//outRole := Role{}
	//err = tx.Where(&superRole).First(&outRole).Error
	//if err != nil {
	//	if gorm.IsRecordNotFoundError(err) {
	//		if err := tx.Create(&Role{Identifier: "superAdmin", Name: "超级管理员", Sequence: 2}).Error; err != nil {
	//			tx.Rollback()
	//			return
	//		}
	//		tx.Where(&superRole).First(&outRole)
	//	} else {
	//		tx.Rollback()
	//		return
	//	}
	//}
	//adminRole := AdminsRole{AdminID: outAdmin.ID, RoleID: outRole.ID}
	//outAdminRole := AdminsRole{}
	//err = tx.Where(&adminRole).First(&outAdminRole).Error
	//if err != nil {
	//	if gorm.IsRecordNotFoundError(err) {
	//		if err := tx.Create(&adminRole).Error; err != nil {
	//			tx.Rollback()
	//			return
	//		}
	//	} else {
	//		tx.Rollback()
	//		return
	//	}
	//}
	//tx.Commit()
}

// ViewInfo .
type ViewInfo struct {
	SchemaName string `gorm:"column:schemaname"`
	ViewName   string `gorm:"column:viewname"`
	ViewOwner  string `gorm:"column:viewowner"`
	Definition string `gorm:"column:definition"`
}

// sql
const (
	ViewSQLStr = "select schemaname, viewname, viewowner, definition from pg_views where viewname = ? limit 1"
)

// HasView .
func HasView(viewName string) (has bool, err error) {
	var res []*ViewInfo
	err = DB().Raw(ViewSQLStr, viewName).Scan(&res).Error
	if err != nil {
		if err == ErrRecordNotFound {
			err = nil
		}
		return
	}
	has = len(res) > 0
	return
}

// HasViewColumn .
func HasViewColumn(viewName, columnName string) (has bool, err error) {
	var res []*ViewInfo
	err = DB().Raw(ViewSQLStr, viewName).Scan(&res).Error
	if err != nil {
		if err == ErrRecordNotFound {
			err = nil
		}
		return
	}
	if len(res) == 0 {
		return
	}
	definition := res[0].Definition
	has = strings.Contains(definition, columnName+",") || strings.Contains(definition, "\""+columnName+"\",")
	return
}
